// Copyright (c) 2011 The Kiwii Authors. All rights reserved.
// Use of this source code is governed by the New BSD License that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

// In order to utilize the existed clint code, we decide to use the same protocol
// specification as the one Redis uses. To see the complete specification, please
// refer to http://www.redis.io/topics/protocol

// Here defined two classes about communication protocol.
// Use class InputMessage to parse received message.
// Use class OutputMessage to make message that will be sent by the client/server.

#ifndef KIWII_PROTOCOL_INCLUDED
#define KIWII_PROTOCOL_INCLUDED

#include <cstddef>
#include <string>
#include <vector>
#include <boost/optional.hpp>
#include <boost/none.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>

namespace kiwii {
  namespace comm {
    namespace protocol {
      
      //Request parser.
      //Must call method ok() to verify the request correction before calling other methods.
      class Request {
      public:
	Request(char const* buf, std::size_t size);
	virtual ~Request() {}

	//Noncopyable
	Request(Request const&) = delete;
	Request& operator=(Request const&) = delete;

	bool ok() const { return ok_; }

	//Returns number of arguments in this request.
	std::size_t argNum() const { return args_.size(); }

	//Returns the nth argument.
	//Throws out_of_range exception if the index beyond the maximum argument number.
	std::string const& arg(int index) const {
	  return args_[index];
	}

	//Return bytes of this message
	std::size_t bytes() const { return bytes_; }

      private:
	bool ok_;
	std::vector<std::string> args_;
	std::size_t bytes_;
      };

      class Reply {
      public:
	Reply() : count_(-1) {}

	Reply const& status(std::string const& status) {
	  content_ = '+' + status + "\r\n";
	  return *this;
	}

	Reply const& error(std::string const& error) {
	  content_ = '-' + error + "\r\n";
	  return *this;
	}

	Reply const& integer(int i) {
	  content_ =  ':' + boost::lexical_cast<std::string>(i) + "\r\n";
	  return *this;
	}

	Reply const& bulk(std::string const& str) {
	  content_ =  boost::str(boost::format("$%d\r\n%s\r\n") % str.size() % str);
	  return *this;
	}

	Reply const& nil() {
	  content_ = "*-1";
	  return *this;
	}

	Reply& multiBulk() {
	  count_ = 0;
	  return *this;
	}

	std::string toString() const { 
	  if (count_ == -1)
	    return content_;
	  else
	    return boost::str(boost::format("*%d\r\n%s") % (count_) % content_); 
	}

	template <typename T>
	friend Reply& operator<<(Reply& r, T const& x) {
	  std::string xs = boost::lexical_cast<std::string>(x);
	  r.content_.append(boost::str(boost::format("$%d\r\n%s\r\n") % xs.size() % xs));
	  r.count_++;
	  return r;
	}

      private:
	std::string content_;
	int count_;
      };

    }
  }
}

#endif
